/* Copyright (C) 2021 Rain */

/* This file is part of XNIX. */

/* 
  XNIX is free software: you can redistribute it and/or modify 
  it under the terms of the GNU General Public License as published by 
  the Free Software Foundation, either version 3 of the License, or 
  (at your option) and later version. 
*/

/*
  XNIX is distributed in the hope that it will be useful, 
  but WITHOUT ANY WARRANTY; without even the implied warranty of 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
  GNU General Public License for more details. 
*/

/*
  You should have received a copy of the GNU General Public License 
   along with XNIX. If not, see <https://www.gnu.org/licenses/>.  
*/




#ifndef _SCHED_H
#define _SCHED_H

#include <kernel/types.h>
#include <kernel/errno.h>

#include <arch/io.h>
#include <arch/sys.h>


#define MAX_TASKS 128


/* 
 * switch_to(next): switch to task next. 
 *
 * STEPS: 
 * 	save that registers that will use. 
 * 	call __do
 *	do: 	set data segments. 
 *		return;
 *
 *	return to next task. 
 *
 * NOTES: 
 * 	we don't need to restore / save registers, because 
 * 	tasks restored them. 
 *
 * 	this function may called from an interrupt-handler 
 * 	(timer IRQ), so we must sti first, else tasks won't 
 * 	switch again. 
 *
 * 	__do maybe changes some registers, like %rbp 
 * 	(maybe doesn't), so we also should save/restore them. 
 * */

#define SAVE_REGS 		\
	"subq $16, %%rsp\n\t"	\
	"pushfq\n\t"		\
	"subq $16, %%rsp\n\t"	\
	"pushq %%rax\n\t"	\
	"pushq %%rcx\n\t"	\
	"pushq %%rdx\n\t"	\
	"pushq %%rbx\n\t"	\
	"pushq %%rbp\n\t"	\
	"pushq %%rsi\n\t"	\
	"pushq %%rdi\n\t"	\
	"pushq %%r8\n\t"	\
	"pushq %%r9\n\t"	\
	"pushq %%r10\n\t"	\
	"pushq %%r11\n\t"	\
	"pushq %%r12\n\t"	\
	"pushq %%r13\n\t"	\
	"pushq %%r14\n\t"	\
	"pushq %%r15\n\t"	\
	"movq %%ds, %%rax\n\t"	\
	"pushq %%rax\n\t"	\
	"movq %%es, %%rax\n\t"	\
	"pushq %%rax\n\t"	\
	"movq %%fs, %%rax\n\t"	\
	"pushq %%rax\n\t"	\
	"movq %%gs, %%rax\n\t"	\
	"pushq %%rax\n\t"

#define RESTORE_REGS 		\
	"popq %%rax\n\t"	\
	"movq %%rax, %%gs\n\t"	\
	"popq %%rax\n\t"	\
	"movq %%rax, %%fs\n\t"	\
	"popq %%rax\n\t"	\
	"movq %%rax, %%es\n\t"	\
	"popq %%rax\n\t"	\
	"movq %%rax, %%ds\n\t"	\
	"popq %%r15\n\t"	\
	"popq %%r14\n\t"	\
	"popq %%r13\n\t"	\
	"popq %%r12\n\t"	\
	"popq %%r11\n\t"	\
	"popq %%r10\n\t"	\
	"popq %%r9\n\t"		\
	"popq %%r8\n\t"		\
	"popq %%rdi\n\t"	\
	"popq %%rsi\n\t"	\
	"popq %%rbp\n\t"	\
	"popq %%rbx\n\t"	\
	"popq %%rdx\n\t"	\
	"popq %%rcx\n\t"	\
	"popq %%rax\n\t"	\
	"addq $16, %%rsp\n\t"	\
	"popfq\n\t"		\
	"addq $16, %%rsp\n\t"


#define switch_to(next) 				\
	__asm__ __volatile__ (				\
		"cli\n\t"				\
		SAVE_REGS				\
		"movq %%rsp, %0\n\t"			\
		"movq %2, %%rsp\n\t"			\
		"leaq 1f(%%rip), %%rax\n\t"		\
		"movq %%rax, %1\n\t"			\
	/* fake return address */			\
		"pushq %3\n\t"				\
	/* get registers in the new stack 		\
	 * note that do_fork also do things  		\
	 * looks like SAVEREGS (copy 			\
	 * struct registers to stack) */		\
		"movq %4, %%rax\n\t"			\
		"movq %%rax, %%ds\n\t"			\
		"movq %%rax, %%es\n\t"			\
		"movq %%rax, %%fs\n\t"			\
		"movq %%rax, %%gs\n\t"			\
		"cmpq $0, need_open_clock(%%rip)\n\t"	\
		"je 2f\n\t"				\
		"movq $0, need_open_clock(%%rip)\n\t"	\
		"movq $0x20, %%rax\n\t"			\
		"movq $0x20, %%rdx\n\t"			\
		"outb %%al, %%dx\n\t"			\
		"2:\n\t"				\
		"sti\n\t"				\
	/* note that if this is the first 		\
	 * run time for this task, we will  		\
	 * return to 'ret_intr' or 			\
	 * ktask_entry */				\
	/* else, we will return to 1f */		\
	/* update the current */			\
		"movq %5, current\n\t"			\
		"retq\n\t"				\
		"1:\n\t"				\
		RESTORE_REGS				\
		: "=m" (current->rsp), 			\
		  "=m" (current->rip)			\
		: "m" ((next)->rsp), 			\
		  "m" ((next)->rip), 			\
		  "r" ((long) (next)->dataseg),		\
		  "r" ((next))				\
		: "ax"					\
	)


/* standard run time for user tasks */
/* 1 jiffy = 10ms */

#define USER_STD_TIME 1

struct task {

	/* died */
#define STATE_ZOMBIE	0

	/* it's running on cpu */
#define STATE_RUNNING 	1

	/* waiting for some resource, and can't be schedule() */
#define STATE_WAIT 	3

	/* you can schedule this task any time */
#define STATE_READY	4

	/* you can't run this task, in this time */
#define STATE_FINISH 	5

	/* task is creating */
#define STATE_IDE	6

	__uint16_t state;

	/* total run time in kernel space (jiffies) */
	__uint32_t ktime;

	/* total run time in user space (0 in kernel tasks) */
	__uint32_t utime;

	/* start time (jiffies) */
	__uint32_t start_time;

	/* by nice, 0.01 - 0.1 sec, default 2 (nice 2) */
	__uint8_t counter;

	/* 0 - 16 */

	__uint32_t nice;

	/* signal (a small bitmap) */
	__uint32_t signal;

	/* mask of signal (can be set by signal() or sigaction(), but SIGKILL) */
	__uint32_t signal_mask;

	int exit_code;

#define INIT_PID 0

	/* pid and his father's pid */
	__uint32_t pid, father;

	/* point to its tss */
	struct tss *tss;

	__uint64_t rsp;
	__uint64_t rip;

	__uint64_t cr3;

	__uint16_t dataseg, codeseg;
};


extern __uint32_t next_pid;

/* unlike linux we use a pointer to save current task */  
extern struct task *current;

extern struct task *task_queues[16];


int task_init(void);
void set_tss(struct tss *, __uint64_t);


/* registers in interrupt-handler's stack */

struct registers {
	__uint64_t gs, fs, es, ds;

	__uint64_t r15, r14, r13, r12;
	__uint64_t r11, r10, r9, r8;

	__uint64_t rdi, rsi, rbp;
	__uint64_t rbx, rdx, rcx, rax;

	/* user task will push this when interrupt happens (for iretq) */
	/* and iretq need these, so this.rip should be the entry point of the user
	 * task (a user function), not the ret_intr. 
	 * */
	__uint64_t rip;
	__uint64_t cs;

	__uint64_t rflags;

	__uint64_t rsp;
	__uint64_t ss;
};


#define ltr(nr) __asm__ __volatile__ ("ltr %%ax" :: "a" (nr))


/* kernel/sched.c */
void ktask_entry(void);
int ktask_create(__uint64_t (* entry) (__uint64_t), __uint64_t);
int utask_create(__uint64_t (* entry) (__uint64_t), __uint64_t);
int do_fork(struct registers *, __uint64_t);


#endif

